/*
 * Title:        CloudScheduler Toolkit
 * Description:  a Toolkit for Modeling and Simulation of Job Scheduling and Resource Provisioning in Cloud System
 * Licence:      GPL - http://www.gnu.org/copyleft/gpl.html
 * Author:       Fuhui Wu
 *
 * Copyright (c) 2013-2014, The National University of Defense Technology, China
 */

package org.nudt.jCloud.simulation;

import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.nudt.jCloud.infrastructure.Slot;
import org.nudt.jCloud.infrastructure.SlotType;
import org.nudt.jCloud.jobScheduler.JobScheduler;
import org.nudt.jCloud.resourceManager.ResManager;
import org.nudt.jCloud.resourceScaler.ResourceScaler;

public class Framework{
	private int id;
	private UserBroker userBroker;
	private ResManager resManager;
	private Map<SlotType, List<Slot>> freeSlots;
	private Map<SlotType, List<Slot>> busySlots;
	private JobScheduler jobScheduler;
	private ResourceScaler resourceScaler;
	
	public static final int SCHEDULING = 0;
	public static final int WAITING = 1;
	public static final int LEASING = 2;
	public static final int RELEASING = 3;

	public Framework() {
		setId(-1);
		resManager = null;
		freeSlots = new HashMap<SlotType, List<Slot>>();
		busySlots = new HashMap<SlotType, List<Slot>>();
		setJobScheduler(null);
	}
	
	public void busySlot(Slot slot){
		SlotType slotType = slot.getSlotType();
		freeSlots.get(slotType).remove(slot);
		if(freeSlots.get(slotType).size()<=0){
			freeSlots.remove(slotType);
		}
		if(busySlots.containsKey(slotType)){
			busySlots.get(slotType).add(slot);
		}else{
			List<Slot> slots = new ArrayList<Slot>();
			slots.add(slot);
			busySlots.put(slotType, slots);
		}
	}
	
	public void freeSlot(Slot slot){
		SlotType slotType = slot.getSlotType();
		busySlots.get(slotType).remove(slot);
		if(busySlots.get(slotType).size()<=0){
			busySlots.remove(slotType);
		}
		if(freeSlots.containsKey(slotType)){
			freeSlots.get(slotType).add(slot);
		}else{
			List<Slot> slots = new ArrayList<Slot>();
			slots.add(slot);
			freeSlots.put(slotType, slots);
		}
	}
	
	public void leaseSlot(Slot slot){
		SlotType slotType = slot.getSlotType();
		if(freeSlots.containsKey(slotType)){
			freeSlots.get(slotType).add(slot);
		}else{
			List<Slot> slotList = new ArrayList<Slot>();
			slotList.add(slot);
			freeSlots.put(slotType, slotList);
		}
	}
	
	public void leaseSlot(SlotType slotType, List<Slot> slotList){
		if(slotList.size()>0){
			if(freeSlots.containsKey(slotType)){
				freeSlots.get(slotType).addAll(slotList);
			}else{
				freeSlots.put(slotType, slotList);
			}
		}
	}
	
	/**
	 * A slot can be released from freeSlots only, but cannot be released from busySlots.
	 * @param slot
	 */
	public void releaseBusySlot(Slot slot){
		if(busySlots.containsKey(slot.getSlotType())==false){
			throw new RuntimeException("Cannot release this slot, it does not contain this slot type.");
		}else{
			if(busySlots.get(slot.getSlotType()).contains(slot)==false){
				throw new RuntimeException("Cannot release this slot.");
			}else{
				busySlots.get(slot.getSlotType()).remove(slot);
				if(busySlots.get(slot.getSlotType()).size()<=0){
					busySlots.remove(slot.getSlotType());
				}
			}
		}
		getResManager().releaseSlot(slot);
	}
	
	public void releaseFreeSlot(Slot slot) {
		if(freeSlots.containsKey(slot.getSlotType())==false){
			throw new RuntimeException("Cannot release this slot, it does not contain this slot type.");
		}else{
			if(freeSlots.get(slot.getSlotType()).contains(slot)==false){
				throw new RuntimeException("Cannot release this slot.");
			}else{
				freeSlots.get(slot.getSlotType()).remove(slot);
				if(freeSlots.get(slot.getSlotType()).size()<=0){
					freeSlots.remove(slot.getSlotType());
				}
			}
		}
		getResManager().releaseSlot(slot);
		
	}
	
	public List<Slot> releaseFreeSlot(SlotType slotType, int detaNum) {
		List<Slot> slotList = new ArrayList<Slot>();
		if(freeSlots.containsKey(slotType)){
			if(freeSlots.get(slotType).size()<=detaNum){
				slotList.addAll(freeSlots.get(slotType));
				freeSlots.remove(slotType);
			}else{
				for(int i=0; i<detaNum; ++i){
					slotList.add(freeSlots.get(slotType).get(0));
					freeSlots.get(slotType).remove(0);
				}			
			}
		}
		return slotList;
	}

	public Slot getOneFreeSlot(SlotType slotType){
		if(getFreeSlots().containsKey(slotType)){
			return getFreeSlots().get(slotType).get(0);							
		}else{
			return null;
		}
	}
	
	public int freeSlotNum(){
		int freeNum = 0;
		for(SlotType slotType : getFreeSlots().keySet()){
			freeNum += getFreeSlots().get(slotType).size();
		}
		return freeNum;
	}
	
	public int busySlotNum(){
		int busyNum = 0;
		for(SlotType slotType : getBusySlots().keySet()){
			busyNum += getBusySlots().get(slotType).size();
		}
		return busyNum;
	}
	
	@Override
	public int hashCode(){
		return getId();
	}
	
	@Override
	public boolean equals(Object o){
		if(o == null){
			return false;
		}
		if(!(o instanceof Framework)){
			return false;
		}
		Framework frwk = (Framework)o;
		if(getId()==frwk.getId()){
			return true;
		}else{
			return false;
		}
	}
	
	public static class SortBySlotNumDec implements Comparator<Framework>{
		public int compare(Framework frwk1, Framework frwk2){
			int frwkNum1 = 0;
			for(SlotType slotType : frwk1.getBusySlots().keySet()){
				frwkNum1 += frwk1.getBusySlots().get(slotType).size();
			}
			for(SlotType slotType : frwk1.getFreeSlots().keySet()){
				frwkNum1 += frwk1.getFreeSlots().get(slotType).size();
			}
			int frwkNum2 = 0;
			for(SlotType slotType : frwk2.getBusySlots().keySet()){
				frwkNum2 += frwk2.getBusySlots().get(slotType).size();
			}
			for(SlotType slotType : frwk2.getFreeSlots().keySet()){
				frwkNum2 += frwk2.getFreeSlots().get(slotType).size();
			}
			if(frwkNum1 < frwkNum2){
				return 1;
			}else if(frwkNum1 > frwkNum2){
				return -1;
			}else{
				return 0;
			}
		}		
	}
	
	public static class SortByStolenNumDec implements Comparator<Framework>{
		public int compare(Framework frwk1, Framework frwk2){
			int frwkNum1 = 0;
			for(SlotType slotType : frwk1.getBusySlots().keySet()){
				frwkNum1 += frwk1.getBusySlots().get(slotType).size();
			}
			int frwkNum2 = 0;
			for(SlotType slotType : frwk2.getBusySlots().keySet()){
				frwkNum2 += frwk2.getBusySlots().get(slotType).size();
			}
			if(frwkNum1 < frwkNum2){
				return 1;
			}else if(frwkNum1 > frwkNum2){
				return -1;
			}else{
				return 0;
			}
		}
	}
		
	/**
	 * @return the jobScheduler
	 */
	public JobScheduler getJobScheduler() {
		return jobScheduler;
	}

	/**
	 * @param jobScheduler the jobScheduler to set
	 */
	public void setJobScheduler(JobScheduler jobScheduler) {
		this.jobScheduler = jobScheduler;
	}

	/**
	 * @return the id
	 */
	public int getId() {
		return id;
	}

	/**
	 * @param id the id to set
	 */
	public void setId(int id) {
		this.id = id;
	}

	/**
	 * @return the resManager
	 */
	public ResManager getResManager() {
		return resManager;
	}

	/**
	 * @param resManager the resManager to set
	 */
	public void setResManager(ResManager resManager) {
		this.resManager = resManager;
	}

	public ResourceScaler getResourceScaler() {
		return resourceScaler;
	}

	public void setResourceScaler(ResourceScaler resourceScaler) {
		this.resourceScaler = resourceScaler;
	}
	
	public Map<SlotType, List<Slot>> getFreeSlots(){
		return freeSlots;
	}
	
	public Map<SlotType, List<Slot>> getBusySlots(){
		return busySlots;
	}

	public UserBroker getUserBroker() {
		return userBroker;
	}

	public void setUserBroker(UserBroker userBroker) {
		this.userBroker = userBroker;
	}
	

}
